#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Document.h"

#include "Class.h"
#include "ImageItem.h"
#include "FileDialog.h"
#include "System.h"
#include "CommandProcessor.h"
#include "WindowSystem.h"
#include "Env.h"
#include "ProgressFilter.h"
#include "OrdColl.h"
#include "FileData.h"
#include "MenuBar.h"
#include "Menu.h"
#include "CmdNo.h"
#include "String.h"
#include "Application.h"
#include "Alert_e.h"
#include "TypeMatcher.h"
#include "Converter.h"
#include "ET_stdio.h"

//---- FormatSaveFileDialog ----------------------------------------------------

const int cIdFormat = cIdFirstUser + 130;

class FormatSaveFileDialog : public FileDialog {
public:
	FormatSaveFileDialog(const char *title= "File Dialog");
	~FormatSaveFileDialog();

	int ShowInWindow2(Clipper*, EvtHandler *eh, Object *op, Converter **dflt);
	void Control(int id, int p, void *v);
	VObject *MakePart(int which);

protected:
	Converter **defaultconverter;
	PopupButton *converterPopup;
	OrdCollection *converters, *converteritems;
	int defaultformatid;
};

FormatSaveFileDialog::FormatSaveFileDialog(const char *tit)
						: FileDialog(tit)
{
	defaultconverter= 0;
	converterPopup= 0;
	converters= 0;
	converteritems= 0;
}

FormatSaveFileDialog::~FormatSaveFileDialog()
{
	SafeDelete(converters);
}

int FormatSaveFileDialog::ShowInWindow2(Clipper *cl,
	EvtHandler *eh, Object *op, Converter **dflt)
{
	defaultconverter= dflt;
	converters= Converter::CollectOutputConverters(op);
	converteritems= new OrdCollection;
	Iter next(converters);
	register Converter *cv;

	defaultformatid= cIdFormat;

	for (int i= 0; cv= (Converter*) next(); i++) {
		converteritems->Add(new MenuButtonItem(cIdFormat+i, cv->AsString()));
		if (*defaultconverter == cv)
			defaultformatid= cIdFormat+i;
	}

	if (converterPopup) {
		converterPopup->SetCollection(converteritems);
		converterPopup->SetValue(defaultformatid, FALSE);
	}

	return FileDialog::ShowInWindow(eFDWrite, cl, eh, "Save with format:");
}

VObject *FormatSaveFileDialog::MakePart(int which)
{
	if (which == eFDEditTitlePart) {
		converterPopup= new PopupButton(cIdFormat, cIdNone,
			new Menu("Output Formats", FALSE, TRUE));
		converterPopup->SetCollection(converteritems);
		converterPopup->SetValue(defaultformatid, FALSE);
		
		return
			new Form(cIdNone, (VObjAlign)(eVObjVBase+eVObjHExpand), gPoint10,
				FileDialog::MakePart(eFDEditTitlePart),
				converterPopup,
				0
			);
	}
	return FileDialog::MakePart(which);
}

void FormatSaveFileDialog::Control(int i, int p, void *v)
{
	if (i == cIdFormat) {
		Converter *c= (Converter*) converters->At((int)v-cIdFormat);
		if (c) {
			*defaultconverter= c;
			if (gDebug)
				fprintf(stderr, "FormatSaveFileDialog::Control: converter == %s\n",
					(*defaultconverter)->AsString());
		}
		return;
	}
	FileDialog::Control(i, p, v);
}

//------------------------------------------------------------------------------

extern SmartBitmap gFileIcon;

static FileDialog *loadDialog, *saveDialog;
static FormatSaveFileDialog *formatSaveDialog;

ONEXIT(Document)
{
	SafeDelete(loadDialog);
	SafeDelete(saveDialog);
	SafeDelete(formatSaveDialog);
}

//---- Document ----------------------------------------------------------------

NewAbstractMetaImpl(Document,Manager, (T(docType), TP(docData)));

Document::Document(const Symbol &dt) : docType(dt), Manager("???")
{
	docData= new FileData((char*)0);
	SetName(GetBaseName());
	SetOnDismiss(eMgrIconize);
}

Document::~Document()
{
	SafeDelete(docData);
}

CommandProcessor *Document::MakeCmdProcessor()
{
	int n= Env::GetValue("Document.UndoLevel", 1);
	if (n == 1)
		return new SingleCommandProcessor;
	//if (n > 100)
		return new UnboundedCommandProcessor;
	//return new BoundedCommandProcessor(n);
}

FileDialog *Document::MakeFileDialog(FileDialogType)
{
	return gApplication->MakeFileDialog();
}

const char *Document::GetBaseName()
{
	return docData->ShortName();
}

u_long Document::UniqueId()
{
	return docData->UniqueId();
}

bool Document::IsUntitled()
{
	return docData->IsUntitled();
}

bool Document::IsConverted()
{
	Object *op= ReturnObjectToStore();
	if (op) {
		Converter *converter= ((FileData*)docData)->GetConverter();
		if (converter == 0 || !converter->CanConvertTo(op))
			return TRUE;
		return FALSE;
	}
	if (GetDocumentType() == cDocTypeAscii && docData->IsAscii())
		return FALSE;
	return docData->Type() != GetDocumentType();
}

void Document::SetData(Data *fd)
{
	if (fd && fd != docData) {
		SafeDelete(docData);
		docData= (Data*) fd->DeepClone();
	}
	SetName(GetBaseName());
}

VObject *Document::DoMakeIconContent()
{
	return DoMakeIcon(GetName());
}

VObject *Document::DoMakeIcon(const char*)
{
	return new ImageItem(docData->GetBitmap());
}

//---- Menus -------------------------------------------------------------------

MenuBar *Document::DoMakeMenuBar()
{
	MenuBar *mb= new MenuBar;
	mb->AddMenuRight(MakeMenu(cHELPMENU));
	mb->AddMenu(MakeMenu(cFILEMENU));
	mb->AddMenu(MakeMenu(cEDITMENU));
	mb->AddMenu(MakeMenu(cAPPMENU));
	return mb;
}

Menu *Document::MakeMenu(int menuId)
{
	Menu *m;

	switch (menuId) {
	case cFILEMENU:
		m= new Menu(cFILEMENU, "File");
		m->AppendItems(
			"New@N",        cNEW,
			"Open ...@O",   cOPEN,
			"-",
			"Load ...@L",   cLOAD,
			"Save@S",       cSAVE,
			"Save As ...",  cSAVEAS,
			"Revert",       cREVERT,
			"Close@W",      cCLOSE,
			"-",
			"Print ...@P",  cPRINT,
			"-",
			"Quit@Q",       cQUIT,
			0);
		break;

	default:
		m= Manager::MakeMenu(menuId);
	}
	return m;
}

void Document::DoSetupMenu(Menu *m)
{
	Manager::DoSetupMenu(m);
	GetCmdP()->DoSetupMenu(m);
	if (Modified()) {
		m->EnableItem(cSAVE);
		if (!IsUntitled())
			m->EnableItem(cREVERT);
	}
	m->EnableItems(cLOAD, cSAVEAS, cPRINT, 0);
}

Command *Document::DoMenuCommand(int cmd)
{
	switch(cmd) {
	case cSAVE:     Save();     break;
	case cSAVEAS:   SaveAs();   break;
	case cLOAD:     Load();     break;
	case cREVERT:   Revert();   break;
	case cCLOSE:    Close();    break;
	default:
		return Manager::DoMenuCommand(cmd);
	}
	return gNoChanges;
}

void Document::CollectParts(Collection *col)
{
	Manager::CollectParts(col);
	if (docData)
		col->Add(docData);
}

void Document::ExtCommand(int c, char *req, char *args, int l, char *&rc, int &rl)
{
	if (strcmp(req, "load") == 0)
		Load(args);
	else if (strcmp(req, "save") == 0)
		Save();
	else
		Manager::ExtCommand(c, req, args, l, rc, rl);
}

bool Document::Modified()
{
	return GetCmdP()->Modified();
}

bool Document::SavedChanges()
{
	if (Modified()) {
		switch (ShowAlert(eAlertCaution, "Save changes to @B%s @P?",
							docData->ShortName())) {
		case cIdYes:
			if (Save())
				return TRUE;
			return FALSE;        // save cancelled -> cancel all
		case cIdNo:
			GetCmdP()->Finish();
			return TRUE;
		case cIdCancel:
			return FALSE;
		}
	}
	return TRUE;
}

bool Document::Load(const char *filename, bool unique)
{
	if (filename) {
		Data *d= new FileData(filename);
		bool b= LoadData(d, unique);
		delete d;
		return b;
	}
	return LoadData(0, unique);
}

bool Document::LoadData(Data *data, bool unique)
{
	if (! SavedChanges())
		return FALSE;

	if (data == 0) {
		if (loadDialog == 0)
			loadDialog= MakeFileDialog(eFDTypeRead);
		if (loadDialog->ShowInWindow(eFDRead, GetWindow(), this) != cIdOk)
			return FALSE;
		data= loadDialog->GetData();
	}

	if (CanLoad(data)) {
		if (data->UniqueId() == 0) {
			ShowAlert(eAlertNote, "something wrong with @B%s @P\n%s",
					data->ShortName(), gSystem->GetErrorStr());
			return FALSE;
		}

		if (unique) {
			Document *doc= (Document*) gApplication->FindManager((int)data->UniqueId());
			if (doc && !DoFileIsAlreadyOpen(doc, data->FullName()))
				return FALSE;
		}

		AboutToLoad(data);

		gProgress->Start(form("Reading: %s", data->ShortName()),
							(int) data->SizeHint());
		bool ok= DoReadData(data);
		if (gProgress->Stop())
			ok= FALSE;      // ProgressDialog was cancelled
		
		if (ok)
			SetData(data);

		gWindowSystem->Update();
	} else
		gApplication->OpenDocument(data->FullName());

	return TRUE;
}

void Document::AboutToLoad(Data*)
{
}

bool Document::Close()   // return TRUE if OK
{
	if (IsOpen()) {
		if (SavedChanges())
			return Manager::Close();
		//return TRUE;
	}
	return FALSE;
}

Object *Document::ReturnObjectToStore()
{
	return 0;
}

bool Document::Save()
{
	if (Modified() && (IsUntitled() || IsConverted()))
		return SaveAs();

	if (! Modified()) {
		ShowAlert(eAlertNote, "No changes since last save");
		return TRUE;
	}

	if (! docData->IsWritable()) {
		ShowAlert(eAlertNote, "Document @B%s @Pis not writable\n%s",
				docData->ShortName(), gSystem->GetErrorStr());
		return FALSE;
	}

	Store();
		
	return TRUE;
}

bool Document::SaveAs()
{
	Object *op= ReturnObjectToStore();
	FileDialog *fd;

	if (op) {
		Converter *converter= ((FileData*)docData)->GetConverter();

		if (formatSaveDialog == 0)
			formatSaveDialog= new FormatSaveFileDialog;


		if (formatSaveDialog->ShowInWindow2(GetWindow(), this, op, &converter) != cIdOk)
			return FALSE;
		((FileData*)docData)->SetConverter(converter);
		
		fd= formatSaveDialog;
	} else {
		if (saveDialog == 0)
			saveDialog= MakeFileDialog(eFDTypeWrite);

		if (saveDialog->ShowInWindow(eFDWrite, GetWindow(), this) != cIdOk)
			return FALSE;
		
		fd= saveDialog;
	}

	((FileData*)docData)->SetName(fd->FileName());
	SetName(GetBaseName());

	if (! docData->IsWritable()) {
		ShowAlert(eAlertNote, "Document @B%s @Pis not writable\n%s",
				docData->ShortName(), gSystem->GetErrorStr());
		return FALSE;
	}

	Store();

	return TRUE;
}

void Document::Revert()
{
	if (Modified() && ShowAlert(eAlertCaution,
		"Discard all changes of @B%s @P?", GetBaseName()) == cIdYes) {
		GetCmdP()->Finish();
		Load(docData->FullName(), FALSE);
	}
}

//---- Load/Store Documents -----------------------------------------------------

bool Document::CanLoad(Data *data)
{
	if (GetDocumentType() == data->Type())
		return TRUE;
	if (GetDocumentType() == cDocTypeAscii && data->IsAscii())
		return TRUE;
	return FALSE;
}

bool Document::DoFileIsAlreadyOpen(Document *shown, const char *nm)
{
	ShowAlert(eAlertNote, "Document @B%s @Pis already open", nm);
	if (shown != this)
		shown->Show();
	return FALSE;
}

long Document::SizeHintForWriting()
{
	return 0;
}

void Document::Store()
{
	bool ok= FALSE;
	Object *op;

	GetCmdP()->Finish();

	long sizehint= SizeHintForWriting();

	if (sizehint > 0)
		gProgress->Start(form("Writing: %s", GetBaseName()), sizehint*2);

	docData->StartUpdate();

	if (sizehint > 0)
		gProgress->Inc(sizehint);

	if (op= ReturnObjectToStore()) {
		Converter *converter= ((FileData*)docData)->GetConverter();
		if (converter) {
			if (gDebug)
				fprintf(stderr, "Document::Store: converter == %s\n",
					converter->AsString());
			ok= converter->ConvertTo(docData, op);
		}
	} else {
		StreamBuf *sb= docData->GetStreamBufForWriting();
		if (sizehint > 0) {
			StreamBuf *osb= sb;
			sb= new ProgressFilter;
			sb->SetBaseStream(osb);
		}
		OStream to(sb, TRUE);
		
		ok= DoWrite(to, docData);
		
		if (ok)
			to << '\n';
	}

	docData->EndUpdate(ok, !Env::GetValue("Document.MakeBackup", FALSE));

	if (sizehint > 0)
		gProgress->Stop();
}

bool Document::DoWrite(OStream& to, Data*)
{
	to << cMagic SP << GetDocumentType().AsString() SP
				<< gApplication->ProgramName() NL;
	return TRUE;
}

bool Document::DoReadData(Data *data)
{
	IStream from(data->GetStreamBufForReading(), 0, TRUE);
	return DoRead(from, data);
}

bool Document::DoRead(IStream &from, Data*)
{
	char c;
	// overread magic cookie
	while (from.get(c))
		if (c == '\n')
			break;
	return TRUE;
}

